/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.dltk.internal.corext.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.internal.ui.DLTKUIException; import org.eclipse.dltk.internal.ui.DLTKUIStatus; import org.eclipse.dltk.ui.DLTKUIPlugin; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * History stores a list of key, object pairs. The list is bounded at size * MAX_HISTORY_SIZE. If the list exceeds this size the eldest element is removed * from the list. An element can be added/renewed with a call to * <code>accessed(Object)</code>. * * The history can be stored to/loaded from an xml file. */ public abstract class History { private static final String DEFAULT_ROOT_NODE_NAME = "histroyRootNode"; //$NON-NLS-1$ private static final String DEFAULT_INFO_NODE_NAME = "infoNode"; //$NON-NLS-1$ private static final int MAX_HISTORY_SIZE = 60; private static DLTKUIException createException(Throwable t, String message) { return new DLTKUIException( DLTKUIStatus.createError(IStatus.ERROR, message, t)); } private final Map fHistory; private final Hashtable fPositions; private final String fFileName; private final String fRootNodeName; private final String fInfoNodeName; public History(String fileName, String rootNodeName, String infoNodeName) { fHistory = new LinkedHashMap(80, 0.75f, true) { private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_HISTORY_SIZE; } }; fFileName = fileName; fRootNodeName = rootNodeName; fInfoNodeName = infoNodeName; fPositions = new Hashtable(MAX_HISTORY_SIZE); } public History(String fileName) { this(fileName, DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME); } public synchronized void accessed(Object object) { fHistory.put(getKey(object), object); rebuildPositions(); } public synchronized boolean contains(Object object) { return fHistory.containsKey(getKey(object)); } public synchronized boolean containsKey(Object key) { return fHistory.containsKey(key); } public synchronized boolean isEmpty() { return fHistory.isEmpty(); } public synchronized Object remove(Object object) { Object removed = fHistory.remove(getKey(object)); rebuildPositions(); return removed; } public synchronized Object removeKey(Object key) { Object removed = fHistory.remove(key); rebuildPositions(); return removed; } /** * Normalized position in history of object denoted by key. The position is * a value between zero and one where zero means not contained in history * and one means newest element in history. The lower the value the older * the element. * * @param key * The key of the object to inspect * @return value in [0.0, 1.0] the lower the older the element */ public synchronized float getNormalizedPosition(Object key) { if (!containsKey(key)) return 0.0f; int pos = ((Integer) fPositions.get(key)).intValue() + 1; // containsKey(key) implies fHistory.size()>0 return (float) pos / (float) fHistory.size(); } /** * Absolute position of object denoted by key in the history or -1 if * !containsKey(key). The higher the newer. * * @param key * The key of the object to inspect * @return value between 0 and MAX_HISTORY_SIZE - 1, or -1 */ public synchronized int getPosition(Object key) { if (!containsKey(key)) return -1; return ((Integer) fPositions.get(key)).intValue(); } public synchronized void load() { IPath stateLocation = DLTKUIPlugin.getDefault().getStateLocation() .append(fFileName); File file = new File(stateLocation.toOSString()); if (file.exists()) { try (InputStreamReader reader = new InputStreamReader( new FileInputStream(file), StandardCharsets.UTF_8)) { load(new InputSource(reader)); } catch (IOException e) { DLTKUIPlugin.log(e); } catch (CoreException e) { DLTKUIPlugin.log(e); } } } public synchronized void save() { IPath stateLocation = DLTKUIPlugin.getDefault().getStateLocation() .append(fFileName); File file = new File(stateLocation.toOSString()); try (OutputStream out = new FileOutputStream(file)) { save(out); } catch (IOException e) { DLTKUIPlugin.log(e); } catch (CoreException e) { DLTKUIPlugin.log(e); } catch (TransformerFactoryConfigurationError e) { // The XML library can be misconficgured (e.g. via // -Djava.endorsed.dirs=C:\notExisting\xerces-2_7_1) DLTKUIPlugin.log(e); } } protected Set getKeys() { return fHistory.keySet(); } protected Collection getValues() { return fHistory.values(); } /** * Store <code>Object</code> in <code>Element</code> * * @param object * The object to store * @param element * The Element to store to */ protected abstract void setAttributes(Object object, Element element); /** * Return a new instance of an Object given <code>element</code> * * @param element * The element containing required information to create the * Object */ protected abstract Object createFromElement(Element element); /** * Get key for object * * @param object * The object to calculate a key for, not null * @return The key for object, not null */ protected abstract Object getKey(Object object); private void rebuildPositions() { fPositions.clear(); Collection values = fHistory.values(); int pos = 0; for (Iterator iter = values.iterator(); iter.hasNext();) { Object element = iter.next(); if (element == null) { continue; } Object key = getKey(element); fPositions.put(key, Integer.valueOf(pos)); pos++; } } private void load(InputSource inputSource) throws CoreException { Element root; try { DocumentBuilder parser = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); root = parser.parse(inputSource).getDocumentElement(); } catch (SAXException e) { throw createException(e, Messages .format(CorextMessages.History_error_read, fFileName)); } catch (ParserConfigurationException e) { throw createException(e, Messages .format(CorextMessages.History_error_read, fFileName)); } catch (IOException e) { throw createException(e, Messages .format(CorextMessages.History_error_read, fFileName)); } if (root == null) return; if (!root.getNodeName().equalsIgnoreCase(fRootNodeName)) { return; } NodeList list = root.getChildNodes(); int length = list.getLength(); for (int i = 0; i < length; ++i) { Node node = list.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { Element type = (Element) node; if (type.getNodeName().equalsIgnoreCase(fInfoNodeName)) { try { Object object = createFromElement(type); if (object != null) { fHistory.put(getKey(object), object); } } catch (Exception me) { if (DLTKCore.DEBUG) { me.printStackTrace(); } } } } } rebuildPositions(); } private void save(OutputStream stream) throws CoreException { try { DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); Element rootElement = document.createElement(fRootNodeName); document.appendChild(rootElement); Iterator values = getValues().iterator(); while (values.hasNext()) { Object object = values.next(); Element element = document.createElement(fInfoNodeName); setAttributes(object, element); rootElement.appendChild(element); } Transformer transformer = TransformerFactory.newInstance() .newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ DOMSource source = new DOMSource(document); StreamResult result = new StreamResult(stream); transformer.transform(source, result); } catch (TransformerException e) { throw createException(e, Messages .format(CorextMessages.History_error_serialize, fFileName)); } catch (ParserConfigurationException e) { throw createException(e, Messages .format(CorextMessages.History_error_serialize, fFileName)); } } }